package cn.bugstack.mybatis.binding;

import cn.bugstack.mybatis.session.Configuration;
import cn.bugstack.mybatis.session.SqlSession;
import cn.hutool.core.lang.ClassScanner;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @Description 映射器注册机。主要负责管理映射器接口（Mapper Interface）与对应映射器实例之间的关系。
 * 作用： 1、管理映射器接口与映射器实例的注册与获取。
 *       2、提供了获取映射器实例的方法。
 * 核心： 1、addMapper(Class<T> type)：向注册中心添加映射器接口。
 *       2、getMapper(Class<T> type, SqlSession sqlSession)：根据映射器接口类型获取对应的映射器实例。
 * 内部数据结构： knownMappers：用于存储映射器接口与对应映射器实例的映射关系。
 * 使用场景：1、在MyBatis初始化阶段，会根据配置文件或注解扫描，将映射器接口注册到 MapperRegistry 中。
 *         2、在应用程序中，可以通过getMapper方法获取映射器实例，从而执行SQL操作。
 * @Classname MapperRegistry
 * @Date 2024/3/28 10:30
 * @Created by aazhao
 */
public class MapperRegistry {

    private Configuration config;

    public MapperRegistry(Configuration config) {
        this.config = config;
    }

    private Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

    public <T> void addMapper(Class<T> type) {
        /* Mapper 必须是接口才会注册 */
        if (type.isInterface()) {
            if (hasMapper(type)) {
                // 如果重复添加了，报错
                throw new RuntimeException("Type " + type + " is already known to the MapperRegistry.");
            }
            // 注册映射器代理工厂
            // eg: type interface cn.bugstack.mybatis.test.dao.IUserDao
            MapperProxyFactory<T> mapperProxyFactory = new MapperProxyFactory<>(type);
            knownMappers.put(type, mapperProxyFactory);
        }
    }

    public void addMappers(String packageName) {
        Set<Class<?>> classSet = ClassScanner.scanPackage(packageName);
        for (Class<?> aClass : classSet) {
            addMapper(aClass);
        }
    }

    public <T> boolean hasMapper(Class<T> type) {
        return knownMappers.containsKey(type);
    }


    /**
     * 根据给定的类型获取对应的Mapper接口实例。
     *
     * @param type 需要获取的Mapper接口的Class对象。
     * @param sqlSession 当前的SqlSession对象，用于执行SQL操作。
     * @return 返回给定类型对应的Mapper接口实例。
     * @throws RuntimeException 如果给定的类型未知或者在创建Mapper实例时发生错误，则抛出异常。
     */
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        // 尝试从已知Mapper中获取指定类型的Mapper代理工厂
        final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
        if (mapperProxyFactory == null) {
            // 如果指定类型的Mapper代理工厂不存在，则抛出异常
            throw new RuntimeException("Type " + type + " is not known to the MapperRegistry.");
        }
        try {
            // 创建并返回指定类型的Mapper接口实例
            return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {
            // 在创建Mapper实例时发生异常，则抛出运行时异常
            throw new RuntimeException("Error getting mapper instance. Cause: " + e, e);
        }
    }

}
